package com.nokia.ion.sdu.cdr_decoder;

import java.io.*;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by vicwang on 2016/8/24.
 */
public class ParseThread implements Runnable {
    private int threadIdx;

    private BufferedInputStream bis;

    private LinkedBlockingQueue<Cdr> queue;

    private long startTime;
    private long endTime;

    private final long skipUnit = 2 * 3600 * 1000;

    private boolean skip = false;

    private Configuration configuration;

    public ParseThread(int threadIdx, long startTime, long endTime, LinkedBlockingQueue<Cdr> queue, Configuration configuration) {
        this.threadIdx = threadIdx;
        this.startTime = startTime;
        this.endTime = endTime;
        this.queue = queue;
        this.configuration = configuration;
    }

    @Override
    public void run() {
        try {
            while (true) {
                File rawFile = Main.nextSourceFile();
                if (rawFile == null) {
                    break;
                } else {
                    processRawFile(rawFile);
                }
            }
        } catch (InterruptedException e) {
            System.out.println(threadIdx + " thread interrupted");
        } finally {
            LoopPrint.infos[threadIdx].file = "";
        }
    }

    private void processRawFile(File rawFile) throws InterruptedException {
//        long start = System.currentTimeMillis();
        try {
            bis = new BufferedInputStream(new FileInputStream(rawFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return;
        }

        try {
            LoopPrint.infos[threadIdx].size = bis.available();
            LoopPrint.infos[threadIdx].file = rawFile.getPath();
            while (bis.available() > 0) {
                if (skip) {
                    skip = false;
                    break;
                }
                Functions.readBytes(bis, 1); // flags
                Functions.readBytes(bis, 1); // msgType
                byte[] msgLength = Functions.readBytes(bis, 2); // msgLength
                Functions.readBytes(bis, 2); // seqNumber
                Functions.readBytes(bis, 2); // transferCmd

                byte[] dataPacket = Functions.readBytes(bis, Functions.toInt(msgLength) - 2); //
                processDataPacket(dataPacket);

                LoopPrint.infos[threadIdx].avalible = bis.available();
            }
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            try {
                bis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

//        long dur = (System.currentTimeMillis() - start) / 1000;
//        System.out.println("file " + rawFile.getName() + " used time " + dur + " sec");
    }

    private void processDataPacket(byte[] gPacket) throws IOException, InterruptedException {
        ByteArrayInputStream bais = new ByteArrayInputStream(gPacket);
        while (bais.available() > 0) { // necessary?
            if (skip) {
                break;
            }
            byte[] dummy = Functions.readBytes(bais, 1);
            byte[] length = Functions.readBytes(bais, 2);
            byte[] recordNumber = Functions.readBytes(bais, 1);
            byte[] recordFormat = Functions.readBytes(bais, 1);
            byte[] formatVersion = Functions.readBytes(bais, 2);
            byte[] dataRecord = Functions.readBytes(bais, Functions.toInt(length) - 4);

            int version = Functions.toInt(formatVersion);
//            print(String.format("recordFormat: %d, formatVersion: %d.", Functions.toInt(recordFormat), version), true);
            if (Functions.toInt(recordFormat) == 1 && (version == 5894
                    || version == 6150
                    || version == 6151
                    || version == 6406
                    || version == 6665
                    || version == 6209)) { // FlexiNG

                ByteArrayInputStream bais2 = new ByteArrayInputStream(dataRecord);
                while (bais2.available() > 0) {
                    byte[] cdrLength = Functions.readBytes(bais2, 2);
                    byte[] topCdr = Functions.readBytes(bais2, Functions.toInt(cdrLength));

                    Cdr cdr = null;
                    ByteArrayInputStream bais3 = new ByteArrayInputStream(topCdr);
                    Functions.readAsnTag(bais3);
                    int cdrLen = Functions.readAsnLen(bais3);
                    byte[] asnCdr = Functions.readBytes(bais3, cdrLen);
                    byte recordType = asnCdr[2];
                    if (recordType == 85) { // PGW-CDR
//                        System.out.println("PGW CDR");
                        cdr = decodePCDR(asnCdr);
                    } else if (recordType == 84) { // SGW-CDR
//                        print("SGW CDR", true);
                        // processNgCdr1(asnCdr);
                    } else {
//                        print("Unknow CDR", true);
                    }
                    bais3.close();

                    if (cdr != null) {
                        if (endTime != 0) {
                            if (configuration.optimize && cdr.recordOpeningTime.getTime() > endTime + skipUnit) {
                                skip = true;
                                break;
                            }

                            if (endTime <= cdr.recordOpeningTime.getTime())
                                continue;
                        }

                        if (startTime != 0) {
                            if (configuration.optimize && cdr.recordOpeningTime.getTime() < startTime - skipUnit){
                                skip = true;
                                break;
                            }

                            if (startTime >= cdr.recordOpeningTime.getTime()) {
                                continue;
                            }
                        }

//                        print(cdr.toString(), true);
//                        print(String.format(" start %s\n open_time %s\n end %s.", Cdr.localFormat.format(new Date(startTime)),
//                                Cdr.localFormat.format(cdr.recordOpeningTime), Cdr.localFormat.format(new Date(endTime))), true);

                        queue.put(cdr);

//                        print("\n", true);
                    }
                }
                bais2.close();
            } else {
//                if (version != 5893 && version != 5126) {
//                    print(String.format("unknown format. recordFormat: %d, formatVersion: %d.", Functions.toInt(recordFormat), version), true);
//                }
            }
        }

        bais.close();
    }

    private Cdr decodePCDR(byte[] aCdr) throws IOException {
        Cdr cdr = new Cdr();

        ByteArrayInputStream bais = new ByteArrayInputStream(aCdr);

        while (bais.available() > 0) {
            int tag = Functions.readAsnTag(bais);
            int len = Functions.readAsnLen(bais);
            byte[] val = Functions.readBytes(bais, len);
            switch (tag) {
                case Cdr.TAG_servedIMSI:
                    cdr.servedIMSI = Functions.toLong(val);
                    break;
                case Cdr.TAG_servedMSISDN:
                    cdr.servedMSISDN = Functions.toLong(val);
                    break;
                case Cdr.TAG_servedIMEISV:
                    cdr.servedIMEISV = Functions.toLong(val);
                    break;
                case Cdr.TAG_accessPointNameNI:
                    String apn = new String(val);
                    cdr.accessPointNameNI = apn;
                    break;
                case Cdr.TAG_rATType:
                    int ratType = Functions.toInt(val);
                    cdr.rATType = ratType;
                    break;
                case Cdr.TAG_nodeId:
                    String nodeId = new String(val);
                    cdr.nodeID = nodeId;
                    break;
                case Cdr.TAG_chargingId:
                    int chargingId = Functions.toInt(val);
                    cdr.chargingId = chargingId;
                    break;
                case Cdr.TAG_servingNodeAddress:
                    String servingNodeAddress = Functions.toIPAddress(val, 2);
                    cdr.servingNodeAddress = servingNodeAddress;
                    break;
                case Cdr.TAG_pGWAddress:
                    String pGWAddress = Functions.toIPAddress(val, 2);
                    cdr.pGWAddress = pGWAddress;
                    break;
                case Cdr.TAG_userLocationInformation:
//                    cdr.userLocationInformation = Long.toHexString(Functions.toLong(val));
                    cdr.userLocationInformation = Functions.toHexString(val);
                    break;
                case Cdr.TAG_recordOpeningTime:
                    String str = Functions.toHexString(val);
                    Date openingTime = Functions.timestampToDate(str);
                    cdr.recordOpeningTime = openingTime;
                    break;
                case Cdr.TAG_duration:
                    int duration = Functions.toInt(val);
                    cdr.duration = duration;
                    break;
                case Cdr.TAG_causeForRecClosing:
                    int cause = Functions.toInt(val);
                    cdr.causeForRecClosing = cause;
                    break;
                case Cdr.TAG_listOfServiceData:
                    //processListOfServiceData(val, cdr);
                    break;
            }
        }

        bais.close();

        return cdr;
    }

    private void processListOfServiceData(byte[] val, Cdr cdr) throws Exception {
        ByteArrayInputStream bais = new ByteArrayInputStream(val);

        int rg = 0, sid = 0;
        Date tor = null;

        while (bais.available() > 0) {
            int tag = Functions.readAsnTag(bais); // 16
            int len = Functions.readAsnLen(bais);
            val = Functions.readBytes(bais, len);
            long uplink = 0, downlink = 0;

            print("", true);
            ByteArrayInputStream bais6 = new ByteArrayInputStream(val);
            while (bais6.available() > 0) {
                tag = Functions.readAsnTag(bais6);
                len = Functions.readAsnLen(bais6);
                val = Functions.readBytes(bais6, len);

                if (tag == 1) { // ratingGroup
                    rg = Functions.toInt(val);
//                            print("ratingGroup: " + rg + ", ", false);
                } else if (tag == 12) { // uplinkVolume
                    uplink = Functions.toLong(val);
//                            print("Uplink: " + uplink + ", ", false);
                } else if (tag == 13) { // downlinkVolume
                    downlink = Functions.toLong(val);
//                            print("Downlink: " + downlink + ", ", false);
                } else if (tag == 14) { // timeOfReport
                    String str = Functions.toHexString(val);
                    tor = Functions.timestampToDate(str);
//                    cdr.timeOfReport = tor;
//                            print(String.format("timerOfReport[%d]: %s, ", val.length, tor.toString()), false);
                } else if (tag == 17) { // serviceIdentifier
                    sid = Functions.toInt(val);
                    cdr.serviceIdentifier = sid;
//                            print("serviceIdentifier: " + sid + ", ", false);
                }
            }

            if (tor.getTime() < Main.startTime.getTime() || tor.getTime() > Main.endTime.getTime()) {
                return;
            }
        }
    }

    static void decodeSCDR(byte[] aCdr) throws Exception {
        int ratType = 0;
        String msisdn = "";
        ByteArrayInputStream bais4 = new ByteArrayInputStream(aCdr);
        while (bais4.available() > 0) {
            int tag = Functions.readAsnTag(bais4);
            int len = Functions.readAsnLen(bais4);
            byte[] val = Functions.readBytes(bais4, len);
            if (tag == 22) { // servedMSISDN
                msisdn = Functions.toHexString(val);
            }
            if (tag == 30) { // ratType
                ratType = Functions.toInt(val);
            }
            if (tag == 12) { // listOfTrafficVolumes
                ByteArrayInputStream bais5 = new ByteArrayInputStream(val);

                while (bais5.available() > 0) {
                    tag = Functions.readAsnTag(bais5); // 16
                    len = Functions.readAsnLen(bais5);
                    val = Functions.readBytes(bais5, len);

                    ByteArrayInputStream bais6 = new ByteArrayInputStream(val);
                    while (bais6.available() > 0) {
                        tag = Functions.readAsnTag(bais6);
                        len = Functions.readAsnLen(bais6);
                        val = Functions.readBytes(bais6, len);

                        if (tag == 6) { // changeTime
                            String str = Functions.toHexString(val).substring(2);
                        }
                    }
                }

                return;
            }
        }
    }

    private static boolean debug = false;
    private void print(String value, boolean newline) {
        if (debug) {
            if (newline) {
                System.out.println(value);
            } else {
                System.out.print(value);
            }
        }
    }
}
